using System;
using System.Collections.Generic;
using System.Threading;

namespace ILoli.IO
{
    [Serializable]
    public sealed class IoCompletionPort
    {
	    /*****************************************************************************
	     * Nested Types.
	     * **************************************************************************/
	    struct DeviceInfo
	    {
	        public System.Object Device;
	        public long CompletionKey;

	        public DeviceInfo(System.Object Device, long CompletionKey)
	        {
		        this.Device = Device;
		        this.CompletionKey = CompletionKey;
	        }
	    };

	    struct CompletionInfo
	    {
	        public long CompletionKey;
            public long NumberOfBytesTransferred;
	        public Overlapped Overlapped;

            public CompletionInfo(long CompletionKey, long NumberOfBytesTransferred, Overlapped Overlapped)
	        {
                this.CompletionKey = CompletionKey;
                this.NumberOfBytesTransferred = NumberOfBytesTransferred;
		        this.Overlapped = Overlapped;
	        }
	    };

	    /*****************************************************************************
	     * Local Variables.
	     * **************************************************************************/
	    List<DeviceInfo> deviceInfos = new List<DeviceInfo>();
	    List<CompletionInfo> completionInfos = new List<CompletionInfo>();
        AutoResetEvent asyncEvent = new AutoResetEvent(false);

        /*****************************************************************************
	     * Helper.
	     * **************************************************************************/
        void GetAndRemoveCompletionStatus(out long NumberOfBytesTransferred, out long CompletionKey, out Overlapped Overlapped)
        {
            NumberOfBytesTransferred = completionInfos[0].NumberOfBytesTransferred;
            CompletionKey = completionInfos[0].CompletionKey;
            Overlapped = completionInfos[0].Overlapped;
            completionInfos.RemoveAt(0);
        }
        bool GetCompletionKey(System.Object Device, out long CompletionKey)
        {
            CompletionKey = 0;

            lock (deviceInfos)
            {
                foreach (DeviceInfo devInfo in deviceInfos)
                {
                    if (devInfo.Device == Device)
                    {
                        CompletionKey = devInfo.CompletionKey;
                        return true;
                    }
                }
            }

            return false;
        }

	    /*****************************************************************************
	     * Interfaces.
	     * **************************************************************************/
	    public bool AttachDevice(System.Object Device, long CompletionKey)
	    {
            if (Device != null)
            {
                lock (deviceInfos)
                {
                    foreach (DeviceInfo devInfo in deviceInfos)
                    {
                        if (devInfo.Device == Device)
                        {
                            if (devInfo.CompletionKey != CompletionKey)
                            {
                                return false;
                            }
                            else
                            {
                                return true;
                            }
                        }
                    }

                    deviceInfos.Add(new DeviceInfo(Device, CompletionKey));

                    return true;
                }
            }

            return false;
	    }

        public void DetachDevice(System.Object Device)
        {
            if (Device != null)
            {
                lock (deviceInfos)
                {
                    for (int iDev = 0; iDev < deviceInfos.Count; iDev++)
                    {
                        if (deviceInfos[iDev].Device == Device)
                        {
                            deviceInfos.RemoveAt(iDev);
                            return;
                        }
                    }
                }
            }
        }

        public void DetachDevice(System.Object Device, int UserData)
	    {
            if (Device != null)
            {
                lock (deviceInfos)
                {
                    for (int iDev = 0; iDev < deviceInfos.Count; iDev++)
                    {
                        if (deviceInfos[iDev].Device == Device)
                        {
                            Overlapped Overlapped = new Overlapped();
                            Overlapped.UserData = UserData;
                            PostQueuedCompletionStatus(0, deviceInfos[iDev].CompletionKey, Overlapped);
                            deviceInfos.RemoveAt(iDev);
                            return;
                        }
                    }
                }
            }
	    }

        public void PostQueuedCompletionStatus(System.Object Device, long NumberOfBytesTransferred, Overlapped Overlapped)
        {
            if (Overlapped != null)
            {
                long CompletionKey;
                if (GetCompletionKey(Device, out CompletionKey))
                {
                    lock (completionInfos)
                    {
                        completionInfos.Add(new CompletionInfo(CompletionKey, NumberOfBytesTransferred, Overlapped));
                        asyncEvent.Set();
                    }
                }
            }
        }

        public void PostQueuedCompletionStatus(long NumberOfBytesTransferred, long CompletionKey, Overlapped Overlapped)
        {
            if (Overlapped != null)
            {
                lock (completionInfos)
                {
                    completionInfos.Add(new CompletionInfo(CompletionKey, NumberOfBytesTransferred, Overlapped));
                    asyncEvent.Set();
                }
            }
        }

        public bool GetQueuedCompletionStatus(out long NumberOfBytesTransferred, out long CompletionKey, out Overlapped Overlapped, int Milliseconds)
        {
            NumberOfBytesTransferred = 0;
            CompletionKey = 0;
            Overlapped = null;

            if (asyncEvent.WaitOne(Milliseconds))
            {
                lock (completionInfos)
                {
                    if (completionInfos.Count > 0)
                    {
                        GetAndRemoveCompletionStatus(out NumberOfBytesTransferred, out CompletionKey, out Overlapped);

                        if (completionInfos.Count > 0)
                        {
                            asyncEvent.Set();
                        }

                        return true;
                    }
                }
            }

            return false;
        }
    }
}